import os
import cv2
import numpy as np
import matplotlib.pyplot as plt
from tkinter import Tk, Label, Button, Frame, messagebox, Scrollbar, Canvas, filedialog
from tkinter.ttk import Progressbar
from PIL import Image, ImageTk
import shutil
import time
import logging
import traceback

# 配置日志记录
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(levelname)s - %(message)s',
    handlers=[
        logging.FileHandler("scan_blur.log"),
        logging.StreamHandler()
    ]
)
logger = logging.getLogger(__name__)

class BlurScanner:
    def __init__(self, root):
        self.root = root
        self.root.title("模糊图片扫描器")
        self.root.geometry("900x700")
        self.root.minsize(800, 600)
        
        # 设置中文字体支持
        plt.rcParams['font.sans-serif'] = ['SimHei']  # 用来正常显示中文标签
        plt.rcParams['axes.unicode_minus'] = False  # 用来正常显示负号
        
        # 初始化变量
        self.photos_dir = "E:/Py/visual/captured_photos"
        self.blur_threshold = None
        self.blur_photos = []
        self.all_photos_data = []
        self.current_photo_index = 0
        self.to_delete = []
        
        # 创建界面
        self._create_widgets()
    
    def _create_widgets(self):
        # 创建顶部控制区
        control_frame = Frame(self.root)
        control_frame.pack(fill="x", padx=10, pady=10)
        
        # 扫描按钮
        self.scan_button = Button(control_frame, text="扫描模糊图片", command=self.scan_blur_photos, font=12, width=20)
        self.scan_button.pack(side="left", padx=5)
        
        # 阈值滑块
        import tkinter.ttk as ttk
        threshold_frame = Frame(control_frame)
        threshold_frame.pack(side="left", padx=20)
        
        Label(threshold_frame, text="模糊百分比阈值: 10%", font=10).pack(side="left")
        
        self.threshold_var = tkinter.IntVar(value=10)
        self.threshold_slider = ttk.Scale(threshold_frame, from_=1, to=50, orient="horizontal", 
                                          variable=self.threshold_var, length=200)
        self.threshold_slider.pack(side="left", padx=10)
        
        # 进度条
        self.progress_var = tkinter.DoubleVar()
        self.progress_bar = Progressbar(control_frame, variable=self.progress_var, length=300)
        self.progress_bar.pack(side="right", fill="x", expand=True, padx=10)
        
        # 创建图像显示区域
        display_frame = Frame(self.root)
        display_frame.pack(fill="both", expand=True, padx=10, pady=5)
        
        # 创建Canvas用于显示图像
        self.canvas = Canvas(display_frame, bg="#f0f0f0")
        self.canvas.pack(side="left", fill="both", expand=True)
        
        # 右侧滚动条
        h_scrollbar = Scrollbar(display_frame, orient="horizontal", command=self.canvas.xview)
        h_scrollbar.pack(side="bottom", fill="x")
        
        v_scrollbar = Scrollbar(display_frame, orient="vertical", command=self.canvas.yview)
        v_scrollbar.pack(side="right", fill="y")
        
        self.canvas.config(xscrollcommand=h_scrollbar.set, yscrollcommand=v_scrollbar.set)
        
        # 创建图像框架
        self.image_frame = Frame(self.canvas)
        self.canvas_window = self.canvas.create_window((0, 0), window=self.image_frame, anchor="nw")
        
        # 绑定事件
        self.image_frame.bind("<Configure>", self._on_frame_configure)
        self.canvas.bind("<Configure>", self._on_canvas_configure)
        
        # 创建底部操作区
        action_frame = Frame(self.root)
        action_frame.pack(fill="x", padx=10, pady=10)
        
        # 控制按钮
        self.prev_button = Button(action_frame, text="上一张", command=self.prev_photo, state="disabled", width=15)
        self.prev_button.pack(side="left", padx=5)
        
        self.next_button = Button(action_frame, text="下一张", command=self.next_photo, state="disabled", width=15)
        self.next_button.pack(side="left", padx=5)
        
        self.delete_check = tkinter.IntVar()
        self.delete_checkbox = tkinter.Checkbutton(action_frame, text="标记为删除", variable=self.delete_check, 
                                                  font=10, command=self.toggle_delete)
        self.delete_checkbox.pack(side="left", padx=20)
        
        # 删除按钮
        self.delete_button = Button(action_frame, text="删除选中图片", command=self.delete_selected_photos, 
                                   state="disabled", width=20, bg="#ff6b6b", fg="white", font=10)
        self.delete_button.pack(side="right", padx=10)
        
        # 统计信息
        self.stats_label = Label(action_frame, text="准备就绪", font=10)
        self.stats_label.pack(side="left", padx=20)
    
    def _on_frame_configure(self, event):
        # 更新画布滚动区域
        self.canvas.configure(scrollregion=self.canvas.bbox("all"))
    
    def _on_canvas_configure(self, event):
        # 调整画布大小时更新内部框架大小
        if event.widget == self.canvas:
            canvas_width = event.width
            self.canvas.itemconfig(self.canvas_window, width=canvas_width)
    
    def calculate_laplacian_variance(self, image_path):
        """
        计算图像的拉普拉斯方差，用于衡量图像清晰度
        """
        try:
            # 验证文件是否存在
            if not os.path.exists(image_path):
                logger.warning(f"文件不存在: {image_path}")
                return None
            
            # 验证文件是否为有效的图像文件
            try:
                # 尝试打开文件以验证格式
                with open(image_path, 'rb') as f:
                    header = f.read(12)
                # 检查常见图像文件头标识
                if not (header.startswith(b'GIF8') or 
                        header.startswith(b'\xff\xd8') or  # JPEG
                        header.startswith(b'BM') or  # BMP
                        header.startswith(b'\x89PNG')):  # PNG
                    logger.warning(f"不是有效的图像文件: {image_path}")
                    return None
            except Exception as e:
                logger.error(f"验证文件格式失败 {image_path}: {str(e)}")
                return None
            
            img = cv2.imread(image_path)
            if img is None:
                logger.warning(f"无法读取图像: {image_path}")
                return None
            
            # 转换为灰度图
            gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
            
            # 计算拉普拉斯方差
            lap_var = cv2.Laplacian(gray, cv2.CV_64F).var()
            
            logger.debug(f"计算图像清晰度 {image_path}: {lap_var:.2f}")
            return lap_var
        except Exception as e:
            logger.error(f"处理文件 {image_path} 时出错: {str(e)}")
            logger.debug(traceback.format_exc())
            return None
    
    def scan_blur_photos(self):
        """
        扫描目录中的所有图片，计算清晰度
        """
        try:
            logger.info("开始扫描模糊图片")
            
            # 清空之前的数据
            self.all_photos_data = []
            self.blur_photos = []
            self.current_photo_index = 0
            self.to_delete = []
            
            # 获取阈值
            threshold_percent = self.threshold_var.get()
            self.stats_label.config(text=f"正在扫描图片，请稍候...")
            self.root.update()
            
            # 验证目录是否存在
            if not os.path.exists(self.photos_dir):
                logger.error(f"图片目录不存在: {self.photos_dir}")
                messagebox.showerror("错误", f"图片目录不存在: {self.photos_dir}")
                self.stats_label.config(text="准备就绪")
                return
            
            # 获取所有图片文件
            image_extensions = ['.jpg', '.jpeg', '.png', '.bmp', '.gif']
            try:
                image_files = [f for f in os.listdir(self.photos_dir) 
                              if os.path.splitext(f)[1].lower() in image_extensions]
            except PermissionError:
                logger.error(f"没有权限访问目录: {self.photos_dir}")
                messagebox.showerror("错误", f"没有权限访问目录: {self.photos_dir}")
                self.stats_label.config(text="准备就绪")
                return
            
            if not image_files:
                logger.info(f"目录中没有找到图片文件: {self.photos_dir}")
                messagebox.showinfo("提示", "目录中没有找到图片文件")
                self.stats_label.config(text="准备就绪")
                return
            
            total_files = len(image_files)
            logger.info(f"找到 {total_files} 个图片文件")
            
            # 计算每张图片的清晰度
            processed_count = 0
            for i, image_file in enumerate(image_files):
                try:
                    image_path = os.path.join(self.photos_dir, image_file)
                    lap_var = self.calculate_laplacian_variance(image_path)
                    
                    if lap_var is not None:
                        self.all_photos_data.append((image_file, lap_var, image_path))
                        processed_count += 1
                except Exception as e:
                    logger.error(f"处理文件 {image_file} 时出错: {str(e)}")
                    logger.debug(traceback.format_exc())
                
                # 更新进度条
                progress = (i + 1) / total_files * 100
                self.progress_var.set(progress)
                # 避免UI卡顿
                if i % 10 == 0:
                    self.root.update()
            
            logger.info(f"成功处理 {processed_count} 张图片")
            
            if not self.all_photos_data:
                messagebox.showinfo("提示", "无法处理任何图片文件")
                self.stats_label.config(text="准备就绪")
                return
            
            # 按清晰度排序（从低到高）
            self.all_photos_data.sort(key=lambda x: x[1])
            
            # 计算阈值
            num_blur_photos = max(1, int(len(self.all_photos_data) * threshold_percent / 100))
            self.blur_threshold = self.all_photos_data[num_blur_photos - 1][1] if self.all_photos_data else 0
            
            # 获取最差的n%图片
            self.blur_photos = [photo for photo in self.all_photos_data if photo[1] <= self.blur_threshold]
            
            logger.info(f"识别出 {len(self.blur_photos)} 张模糊图片 (阈值: {self.blur_threshold:.2f}, 百分比: {threshold_percent}%)")
            
            # 更新统计信息
            self.stats_label.config(text=f"发现 {len(self.blur_photos)} 张模糊图片 (阈值: {self.blur_threshold:.2f})")
            
            # 启用控制按钮
            self.prev_button.config(state="normal")
            self.next_button.config(state="normal")
            self.delete_button.config(state="normal" if self.blur_photos else "disabled")
            
            # 显示第一张图片
            if self.blur_photos:
                self.display_photo(0)
            
        except Exception as e:
            logger.error(f"扫描过程中出错: {str(e)}")
            logger.debug(traceback.format_exc())
            messagebox.showerror("错误", f"扫描过程中出错: {str(e)}")
            self.stats_label.config(text="准备就绪")
    
    def display_photo(self, index):
        """
        显示指定索引的图片
        """
        if index < 0 or index >= len(self.blur_photos):
            logger.warning(f"无效的图片索引: {index}")
            return
        
        self.current_photo_index = index
        photo_name, lap_var, photo_path = self.blur_photos[index]
        
        logger.info(f"显示图片: {photo_name} (清晰度: {lap_var:.2f})")
        
        # 清除之前的图像
        for widget in self.image_frame.winfo_children():
            widget.destroy()
        
        # 显示图片信息
        info_label = Label(self.image_frame, text=f"图片: {photo_name}\n拉普拉斯方差: {lap_var:.2f}\n" 
                          f"({index + 1}/{len(self.blur_photos)})")
        info_label.pack(pady=10)
        
        try:
            # 验证文件是否仍然存在
            if not os.path.exists(photo_path):
                error_label = Label(self.image_frame, text="错误: 图片文件不存在", fg="red")
                error_label.pack(pady=20)
                logger.error(f"图片文件不存在: {photo_path}")
                return
            
            # 加载并显示图片
            image = Image.open(photo_path)
            
            # 调整图片大小以适应显示区域
            max_width = 700
            max_height = 500
            image.thumbnail((max_width, max_height), Image.LANCZOS)
            
            photo = ImageTk.PhotoImage(image)
            
            image_label = Label(self.image_frame, image=photo)
            image_label.image = photo  # 保持引用，防止被垃圾回收
            image_label.pack()
            
            # 更新删除复选框状态
            if photo_path in self.to_delete:
                self.delete_check.set(1)
            else:
                self.delete_check.set(0)
                
        except Exception as e:
            logger.error(f"显示图片 {photo_path} 时出错: {str(e)}")
            logger.debug(traceback.format_exc())
            error_label = Label(self.image_frame, text=f"无法显示图片: {str(e)}", fg="red")
            error_label.pack(pady=20)
    
    def prev_photo(self):
        """
        显示上一张图片
        """
        if self.current_photo_index > 0:
            self.display_photo(self.current_photo_index - 1)
    
    def next_photo(self):
        """
        显示下一张图片
        """
        if self.current_photo_index < len(self.blur_photos) - 1:
            self.display_photo(self.current_photo_index + 1)
    
    def toggle_delete(self):
        """
        切换当前图片的删除状态
        """
        if not self.blur_photos:
            return
        
        photo_path = self.blur_photos[self.current_photo_index][2]
        
        if self.delete_check.get():
            if photo_path not in self.to_delete:
                self.to_delete.append(photo_path)
        else:
            if photo_path in self.to_delete:
                self.to_delete.remove(photo_path)
    
    def delete_selected_photos(self):
        """
        删除选中的图片及对应的.txt文件
        """
        if not self.to_delete:
            messagebox.showinfo("提示", "没有选中要删除的图片")
            return
        
        # 记录要删除的文件信息
        files_to_delete = []
        for photo_path in self.to_delete:
            files_to_delete.append(photo_path)
            txt_path = os.path.splitext(photo_path)[0] + ".txt"
            if os.path.exists(txt_path):
                files_to_delete.append(txt_path)
        
        logger.info(f"准备删除 {len(files_to_delete)} 个文件")
        
        # 二次确认
        confirm = messagebox.askyesno(
            "确认删除", 
            f"确定要删除选中的 {len(self.to_delete)} 张图片及其对应的.txt标注文件吗？\n" 
            f"总共将删除 {len(files_to_delete)} 个文件\n" 
            f"此操作不可撤销！"
        )
        
        if not confirm:
            logger.info("用户取消了删除操作（第一次确认）")
            return
        
        # 再次确认
        final_confirm = messagebox.askyesno(
            "最终确认", 
            "请再次确认删除操作！这将永久删除选中的文件。"
        )
        
        if not final_confirm:
            logger.info("用户取消了删除操作（第二次确认）")
            return
        
        try:
            deleted_count = 0
            error_count = 0
            skipped_count = 0
            
            # 更新状态
            self.stats_label.config(text=f"正在删除 {len(self.to_delete)} 个文件...")
            self.root.update()
            
            # 删除文件
            for i, photo_path in enumerate(self.to_delete):
                try:
                    # 安全检查：确保只在指定目录内删除文件
                    if not os.path.abspath(photo_path).startswith(os.path.abspath(self.photos_dir)):
                        logger.error(f"安全检查失败：尝试删除目录外的文件 {photo_path}")
                        skipped_count += 1
                        continue
                    
                    # 删除图片文件
                    if os.path.exists(photo_path):
                        os.remove(photo_path)
                        deleted_count += 1
                        logger.info(f"已删除图片文件: {photo_path}")
                    else:
                        logger.warning(f"图片文件不存在，跳过: {photo_path}")
                        skipped_count += 1
                    
                    # 删除对应的.txt文件（如果存在）
                    txt_path = os.path.splitext(photo_path)[0] + ".txt"
                    # 再次进行安全检查
                    if os.path.abspath(txt_path).startswith(os.path.abspath(self.photos_dir)):
                        if os.path.exists(txt_path):
                            os.remove(txt_path)
                            deleted_count += 1
                            logger.info(f"已删除标注文件: {txt_path}")
                        else:
                            logger.debug(f"标注文件不存在，跳过: {txt_path}")
                    else:
                        logger.error(f"安全检查失败：尝试删除目录外的标注文件 {txt_path}")
                        skipped_count += 1
                    
                    # 更新进度
                    progress = (i + 1) / len(self.to_delete) * 100
                    self.progress_var.set(progress)
                    # 避免UI卡顿
                    if i % 5 == 0:
                        self.root.update()
                    
                    # 短暂延迟，确保文件系统操作完成
                    time.sleep(0.05)
                    
                except PermissionError:
                    logger.error(f"没有权限删除文件 {photo_path}")
                    error_count += 1
                except Exception as e:
                    logger.error(f"删除文件 {photo_path} 时出错: {str(e)}")
                    logger.debug(traceback.format_exc())
                    error_count += 1
            
            # 清除删除列表
            self.to_delete = []
            
            logger.info(f"删除完成: 成功 {deleted_count}, 失败 {error_count}, 跳过 {skipped_count}")
            
            # 重新扫描以更新显示
            self.scan_blur_photos()
            
            # 显示结果
            result = messagebox.showinfo(
                "删除完成", 
                f"成功删除 {deleted_count} 个文件\n" 
                f"删除失败 {error_count} 个文件\n" 
                f"跳过 {skipped_count} 个文件\n\n"
                f"程序将在点击确定后自动退出"
            )
            
            # 删除完成后自动退出程序
            logger.info("删除操作完成，程序自动退出")
            self.root.after(1000, self.root.destroy)  # 延迟1秒后退出，确保用户看到消息
            return
            
        except Exception as e:
            logger.error(f"删除过程中出错: {str(e)}")
            logger.debug(traceback.format_exc())
            messagebox.showerror("错误", f"删除过程中出错: {str(e)}")
        finally:
            self.stats_label.config(text="准备就绪")

if __name__ == "__main__":
    # 确保导入tkinter
    import tkinter
    
    # 创建主窗口
    root = Tk()
    
    # 创建应用实例
    app = BlurScanner(root)
    
    # 运行主循环
    root.mainloop()